package com.jl;

import com.google.auto.service.AutoService;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.*;
import java.util.*;

/**
 * 自动生成spring.factories文件
 */
@AutoService(Processor.class)
@SupportedOptions({"debug", "verify"})
public class JLSpringFactorles extends AbstractProcessor {

    private static final String SERVICES_PATH = "META-INF/spring.factories";

    private Map<String, Set<String>> providers = new HashMap<>();

    /**
     * 会被回调处理的注解（项目至少有以下一个注解才会加载类)
     */
    Set<String> annotations = new HashSet<>(
            Arrays.asList("org.springframework.stereotype.Component", "org.springframework.stereotype.Service"
                    , "org.springframework.stereotype.Controller", "org.springframework.web.bind.annotation.RestController"
                    , "org.springframework.web.bind.annotation.RestControllerAdvice", "org.springframework.context.annotation.Configuration"
                    , "org.springframework.cloud.openfeign.FeignClient"
            )
    );

    /**
     * 初始化会被回调处理的注解
     *
     * @return
     */
    @Override
    public ImmutableSet<String> getSupportedAnnotationTypes() {
        return ImmutableSet.copyOf(annotations);
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 处理过程
     *
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            if (roundEnv.processingOver()) {
                generateConfigFiles();
            } else {
                exec(roundEnv);
            }
        } catch (Exception e) {
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            fatalError(writer.toString());
        }
        return true;
    }

    /**
     * @param roundEnv 可以说是所有类的集合
     */
    private void exec(RoundEnvironment roundEnv) {
        Set<? extends Element> rootElements = roundEnv.getRootElements();
        if (rootElements.size() == 0) {
            return;
        }
        for (Element e : rootElements) {
            TypeElement providerImplementer = (TypeElement) e;
            List<? extends AnnotationMirror> annotationMirrors = providerImplementer.getAnnotationMirrors();
            //过滤接口
            if (providerImplementer.getKind().isInterface()) {
                //是否是注解，是则过滤
                if (providerImplementer.getKind() == ElementKind.ANNOTATION_TYPE) {
                    continue;
                }
                //是否是feign，不是则过滤
                boolean isFeign = isAnnotation(annotationMirrors, "org.springframework.cloud.openfeign.FeignClient");
                if (!isFeign) {
                    continue;
                }
            }
            //过滤启动类
            boolean isApp = isAnnotation(annotationMirrors, "org.springframework.boot.autoconfigure.SpringBootApplication");
            if (isApp) {
                continue;
            }
            boolean b = checkFatherComponent(annotationMirrors);
            if (b) {
                TypeElement providerType = processingEnv.getElementUtils().getTypeElement(EnableAutoConfiguration.class.getName());
                providers.computeIfAbsent(getBinaryName(providerType), key -> new HashSet<>())
                        .add(getBinaryName(providerImplementer));
            }
        }
    }

    /**
     * 递归判断是否有某个注解
     *
     * @return
     */
    public boolean isAnnotation(List<? extends AnnotationMirror> annotationMirrors, String annotationName) {
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            String simpleName = annotationMirror.getAnnotationType().asElement().asType().toString();
            //String simpleName = annotationMirror.getAnnotationType().asElement().getSimpleName();
            if (simpleName.equals("java.lang.annotation.Documented") || simpleName.equals("java.lang.annotation.Retention") || simpleName.equals("java.lang.annotation.Target")) {
                continue;
            }
            //判断自身类的注解
            if (simpleName.equals(annotationName)) {
                return true;
            }
            //判断注解的父注解
            List<? extends AnnotationMirror> annotationMirrorsp = annotationMirror.getAnnotationType().asElement().getAnnotationMirrors();
            boolean b = isAnnotation(annotationMirrorsp, annotationName);
            if (b) {
                return b;
            }
        }
        return false;
    }

    /**
     * 递归判断注解是否可生成spring.factories文件
     *
     * @param annotationMirrors
     * @return
     */
    public boolean checkFatherComponent(List<? extends AnnotationMirror> annotationMirrors) {
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            //判断自身类的注解
            String simpleName = annotationMirror.getAnnotationType().asElement().asType().toString();
            //String simpleName = annotationMirror.getAnnotationType().asElement().getSimpleName();
            if (simpleName.equals("java.lang.annotation.Documented") || simpleName.equals("java.lang.annotation.Retention") || simpleName.equals("java.lang.annotation.Target")) {
                continue;
            }
            if (simpleName.equals("org.springframework.stereotype.Component") || simpleName.equals("org.springframework.cloud.openfeign.FeignClient")) {
                return true;
            }
            //判断注解的父注解
            List<? extends AnnotationMirror> annotationMirrorsp = annotationMirror.getAnnotationType().asElement().getAnnotationMirrors();
            boolean b = checkFatherComponent(annotationMirrorsp);
            if (b) {
                return b;
            }
        }
        return false;
    }

    private void generateConfigFiles() {
        Filer filer = processingEnv.getFiler();
        try {
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
                    SERVICES_PATH);
            OutputStream out = fileObject.openOutputStream();
            Files.writeServiceFile(providers, out);
            out.close();
            log("Wrote to: " + fileObject.toUri());
        } catch (IOException e) {
            fatalError("Unable to create " + SERVICES_PATH + ", " + e);
            return;
        }
    }

    private String getBinaryName(TypeElement element) {
        return getBinaryNameImpl(element, element.getSimpleName().toString());
    }

    private String getBinaryNameImpl(TypeElement element, String className) {
        Element enclosingElement = element.getEnclosingElement();
        if (enclosingElement instanceof PackageElement) {
            PackageElement pkg = (PackageElement) enclosingElement;
            if (pkg.isUnnamed()) {
                return className;
            }
            return pkg.getQualifiedName() + "." + className;
        }
        TypeElement typeElement = (TypeElement) enclosingElement;
        return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
    }

    private void log(String msg) {
        if (processingEnv.getOptions().containsKey("debug")) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
        }
    }

    private void fatalError(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg);
    }


    public static class Files {
        static void writeServiceFile(Map<String, Set<String>> services, OutputStream output) throws IOException {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, Charsets.UTF_8));
            for (Map.Entry<String, Set<String>> entry : services.entrySet()) {
                writer.write(entry.getKey() + "=\\");
                boolean first = true;
                for (String service : entry.getValue()) {
                    if (first) {
                        writer.newLine();
                        writer.write(service);
                        first = false;
                    } else {
                        writer.write(",\\");
                        writer.newLine();
                        writer.write(service);
                    }
                }
                writer.newLine();
            }
            writer.flush();
        }
    }
}